home *** CD-ROM | disk | FTP | other *** search
/ Apple Developer Connection Student Program / ADC Tools Sampler CD Disk 3 1999.iso / Metrowerks CodeWarrior / Java Support / Java_Source / Java2 / src / javax / swing / JMenu.java < prev    next >
Encoding:
Java Source  |  1999-05-28  |  40.7 KB  |  1,322 lines  |  [TEXT/CWIE]

  1. /*
  2.  * @(#)JMenu.java    1.111 98/09/20
  3.  *
  4.  * Copyright 1997, 1998 by Sun Microsystems, Inc.,
  5.  * 901 San Antonio Road, Palo Alto, California, 94303, U.S.A.
  6.  * All rights reserved.
  7.  *
  8.  * This software is the confidential and proprietary information
  9.  * of Sun Microsystems, Inc. ("Confidential Information").  You
  10.  * shall not disclose such Confidential Information and shall use
  11.  * it only in accordance with the terms of the license agreement
  12.  * you entered into with Sun.
  13.  */
  14.  
  15. package javax.swing;
  16.  
  17. import java.awt.Component;
  18. import java.awt.Container;
  19. import java.awt.Dimension;
  20. import java.awt.Frame;
  21. import java.awt.Graphics;
  22. import java.awt.Point;
  23. import java.awt.Polygon;
  24. import java.awt.Rectangle;
  25. import java.awt.event.*;
  26. import java.beans.*;
  27.  
  28. import java.util.*;
  29.  
  30. import java.io.Serializable;
  31. import java.io.ObjectOutputStream;
  32. import java.io.ObjectInputStream;
  33. import java.io.IOException;
  34.  
  35. import javax.swing.event.*;
  36. import javax.swing.plaf.*;
  37. import javax.swing.plaf.basic.*;
  38. import javax.accessibility.*;
  39.  
  40.  
  41. /**
  42.  * An implementation of a menu -- a popup window containing <code>JMenuItem</code>s that
  43.  * is displayed when the user selects an item on the <code>JMenuBar</code>. In addition
  44.  * to JMenuItems, a JMenu can also contain <code>JSeparator</code>s. 
  45.  * <p>
  46.  * In essence, a menu is a button with an associated JPopupMenu.
  47.  * When the "button" is pressed, the JPopupMenu appears. If the
  48.  * "button" is on the JMenuBar, the menu is a top-level window.
  49.  * If the "button" is another menu item, then the JPopupMenu is
  50.  * "pull-right" menu.
  51.  * <p>
  52.  * For the keyboard keys used by this component in the standard Look and
  53.  * Feel (L&F) renditions, see the
  54.  * <a href="doc-files/Key-Index.html#JMenu">JMenu</a> key assignments.
  55.  * <p>
  56.  * <strong>Warning:</strong>
  57.  * Serialized objects of this class will not be compatible with 
  58.  * future Swing releases.  The current serialization support is appropriate
  59.  * for short term storage or RMI between applications running the same
  60.  * version of Swing.  A future release of Swing will provide support for
  61.  * long term persistence.
  62.  *
  63.  * @version 1.111 09/20/98
  64.  * @author Georges Saab
  65.  * @author David Karlton
  66.  * @author Arnaud Weber
  67.  * @see JMenuItem
  68.  * @see JSeparator
  69.  * @see JMenuBar
  70.  * @see JPopupMenu
  71.  */
  72. public class JMenu extends JMenuItem implements Accessible,MenuElement
  73. {
  74.     /**
  75.      * @see #getUIClassID
  76.      * @see #readObject
  77.      */
  78.     private static final String uiClassID = "MenuUI";
  79.  
  80.     /*
  81.      * The popup menu portion of the menu.
  82.      */
  83.     private JPopupMenu popupMenu;
  84.  
  85.     /*
  86.      * The button's model listeners.
  87.      */
  88.     private ChangeListener menuChangeListener = null;
  89.  
  90.     /*
  91.      * Only one MenuEvent is needed per menu instance since the
  92.      * event's only state is the source property.  The source of events
  93.      * generated is always "this".
  94.      */
  95.     private MenuEvent menuEvent = null;
  96.  
  97.     /* Registry of listeners created for Action-JMenuItem
  98.      * linkage.  This is needed so that references can
  99.      * be cleaned up at remove time to allow GC.
  100.      */
  101.     private static Hashtable listenerRegistry = null;
  102.  
  103.     /**
  104.      * Creates a new JMenu with no text.
  105.      */
  106.     public JMenu() {
  107.         this("");
  108.     }
  109.  
  110.     /**
  111.      * Creates a new JMenu with the supplied string as its text
  112.      *
  113.      * @param s  The text for the menu label
  114.      */
  115.     public JMenu(String s) {
  116.         init(s, null);
  117.         updateUI();
  118.     }
  119.  
  120.     /**
  121.      * Creates a new JMenu with the supplied string as its text
  122.      * and specified as a tear-off menu or not.
  123.      *
  124.      * @param s The text for the menu label
  125.      * @param b can the menu be torn off (not yet implemented)
  126.      */
  127.     public JMenu(String s, boolean b) {
  128.         this(s);
  129.     }
  130.  
  131.     
  132.     /**
  133.      * Notification from the UIFactory that the L&F has changed. 
  134.      * Called to replace the UI with the latest version from the 
  135.      * UIFactory.
  136.      *
  137.      * @see JComponent#updateUI
  138.      */
  139.     public void updateUI() {
  140.         setUI((MenuItemUI)UIManager.getUI(this));
  141.  
  142.         if ( popupMenu != null )
  143.           {
  144.             popupMenu.setUI((PopupMenuUI)UIManager.getUI(popupMenu));
  145.           }
  146.  
  147.     }
  148.  
  149.  
  150.     /**
  151.      * Returns the name of the L&F class that renders this component.
  152.      *
  153.      * @return "MenuUI"
  154.      * @see JComponent#getUIClassID
  155.      * @see UIDefaults#getUI
  156.      */
  157.     public String getUIClassID() {
  158.         return uiClassID;
  159.     }
  160.  
  161.     //    public void repaint(long tm, int x, int y, int width, int height) {
  162.     //        Thread.currentThread().dumpStack();
  163.     //        super.repaint(tm,x,y,width,height);
  164.     //    }
  165.  
  166.     /**
  167.      * Set the data model for the "menu button" -- the label
  168.      * that the user clicks to open or close the menu.
  169.      *
  170.      * @param m the ButtonModel
  171.      * @see #getModel
  172.      * @beaninfo
  173.      * description: The menu's model
  174.      *       bound: true
  175.      *      expert: true
  176.      *      hidden: true
  177.      */
  178.     public void setModel(ButtonModel newModel) {
  179.         ButtonModel oldModel = getModel();
  180.  
  181.         super.setModel(newModel);
  182.  
  183.         if (oldModel != null) {
  184.             oldModel.removeChangeListener(menuChangeListener);
  185.             menuChangeListener = null;
  186.         }
  187.         
  188.         model = newModel;
  189.         
  190.         if (newModel != null) {
  191.             menuChangeListener = createMenuChangeListener();
  192.             newModel.addChangeListener(menuChangeListener);
  193.         }
  194.     }
  195.  
  196.     /**
  197.      * Returns true if the menu is currently selected (popped up).
  198.      *
  199.      * @return true if the menu is open, else false
  200.      */
  201.     public boolean isSelected() {
  202.         return getModel().isSelected();
  203.     }
  204.  
  205.     /**
  206.      * Sets the selection status of the menu.
  207.      *
  208.      * @param b  a boolean value -- true to select the menu and 
  209.      *           open it, false to unselect the menu and close it
  210.      * @beaninfo
  211.      *      description: When the menu is selected, its popup child is shown.
  212.      *           expert: true
  213.      *           hidden: true
  214.      */
  215.     public void setSelected(boolean b) {
  216.         ButtonModel model = getModel();
  217.         boolean oldValue = model.isSelected();
  218.  
  219.         if ((accessibleContext != null) && (oldValue != b)) {
  220.             if (b) {
  221.                  accessibleContext.firePropertyChange(
  222.                          AccessibleContext.ACCESSIBLE_STATE_PROPERTY,
  223.                          null, AccessibleState.SELECTED);
  224.             } else {
  225.                  accessibleContext.firePropertyChange(
  226.                          AccessibleContext.ACCESSIBLE_STATE_PROPERTY,
  227.                          AccessibleState.SELECTED, null);
  228.             }
  229.         }
  230.         if (b != model.isSelected()) {
  231.             getModel().setSelected(b);
  232.         }
  233.     }
  234.  
  235.     /**
  236.      * Returns true if the menu's popup window is visible.
  237.      *
  238.      * @return true if the menu is visible, else false
  239.      */
  240.     public boolean isPopupMenuVisible() {
  241.         ensurePopupMenuCreated();
  242.         return popupMenu.isVisible();
  243.     }
  244.  
  245.     /**
  246.      * Set the visibility of the Menu's popup portion.  The popup
  247.      * may only be made visible if the menu is itself showing on
  248.      * the screen.
  249.      *
  250.      * @param b  a boolean value -- true to make the menu visible,
  251.      *           false to hide it
  252.      * @beaninfo
  253.      *      description: The popup menu's visibility
  254.      *           expert: true
  255.      *           hidden: true
  256.      */
  257.     public void setPopupMenuVisible(boolean b) {
  258.     if (!isEnabled())
  259.         return;
  260.         boolean isVisible = isPopupMenuVisible();
  261.         if (b != isVisible) {
  262.             ensurePopupMenuCreated();
  263.             // Set location of popupMenu (pulldown or pullright)
  264.             //  Perhaps this should be dictated by L&F
  265.             int x = 0;
  266.             int y = 0;
  267.             if ((b==true) && isShowing()) {
  268.                 Container parent = getParent();
  269.                 Dimension s = getSize();
  270.                 if (parent instanceof JPopupMenu) {
  271.                     // System.out.println("Pullright: " + getText());
  272.                     x = s.width;
  273.                     y = 0;
  274.                 } else {
  275.                     // System.out.println("Pulldown: " + getText());
  276.                     x = 0;
  277.                     y = s.height;
  278.                 }
  279.                 
  280.                 popupMenu.show(this, x, y);
  281.             } else {
  282.                 popupMenu.setVisible(false);
  283.             }
  284.         }
  285.  
  286.     }
  287.  
  288.     private int delay = 0;
  289.  
  290.     /**
  291.      * Returns the suggested delay before the menu's PopupMenu is popped up or down.
  292.      *
  293.      * @return an int -- the number of milliseconds to delay
  294.      */
  295.     public int getDelay() {
  296.         return delay;
  297.     }
  298.     
  299.     /**
  300.      * Sets the suggested delay before the menu's PopupMenu is popped up or down.
  301.      *
  302.      * @param       d the number of milliseconds to delay
  303.      * @exception   IllegalArgumentException if the value of 
  304.      *                       <code>d</code> is less than 0.
  305.      * @beaninfo
  306.      *      description: The delay between menu selection and making the popup menu visible
  307.      *           expert: true
  308.      */
  309.     public void setDelay(int d) {
  310.         if (d < 0)
  311.             throw new IllegalArgumentException("Delay must be a positive integer");
  312.         
  313.         delay = d;
  314.     }
  315.  
  316.     /**
  317.      * The window-closing listener for the popup.
  318.      *
  319.      * @see WinListener
  320.      */
  321.     protected WinListener popupListener;
  322.  
  323.     private void ensurePopupMenuCreated() {
  324.         if (popupMenu == null) {            
  325.             final JMenu thisMenu = this;
  326.             this.popupMenu = new JPopupMenu();
  327.             popupMenu.setInvoker(this);
  328.             popupListener = createWinListener(popupMenu);
  329.             popupMenu.addPopupMenuListener(new PopupMenuListener() {
  330.                 public void popupMenuWillBecomeVisible(PopupMenuEvent e) {
  331.                 }
  332.                 public void popupMenuWillBecomeInvisible(PopupMenuEvent e) {
  333.                 }
  334.                 public void popupMenuCanceled(PopupMenuEvent e) {
  335.                     fireMenuCanceled();
  336.                 }
  337.             });
  338.         }
  339.     }
  340.  
  341.     /**
  342.      * Set the location of the popup component
  343.      *
  344.      * @param x the x coordinate of the popup's new position
  345.      * @param y the y coordinate of the popup's new position
  346.      */
  347.     public void setMenuLocation(int x, int y) {
  348.         popupMenu.setLocation(x, y);
  349.     }
  350.  
  351.     /**
  352.      * Appends a menuitem to the end of this menu. 
  353.      * Returns the menuitem added.
  354.      *
  355.      * @param menuItem the JMenuitem to be added
  356.      * @return the JMenuItem added
  357.      */
  358.     public JMenuItem add(JMenuItem menuItem) {
  359.         AccessibleContext ac = menuItem.getAccessibleContext();
  360.         ac.setAccessibleParent(this);
  361.         ensurePopupMenuCreated();
  362.         return popupMenu.add(menuItem);
  363.     }
  364.  
  365.     /**
  366.      * Appends a component to the end of this menu.
  367.      * Returns the component added.
  368.      *
  369.      * @param c the Component to add
  370.      * @return the Component added
  371.      */
  372.     public Component add(Component c) {
  373.      if (c instanceof JComponent) {    
  374.         AccessibleContext ac = ((JComponent) c).getAccessibleContext();
  375.         ac.setAccessibleParent(this);
  376.     }
  377.         ensurePopupMenuCreated();
  378.         popupMenu.add(c);
  379.         return c;
  380.     }
  381.  
  382.     /**
  383.      * Creates a new menuitem with the specified text and appends
  384.      * it to the end of this menu.
  385.      *  
  386.      * @param s the string for the menuitem to be added
  387.      */
  388.     public JMenuItem add(String s) {
  389.         return add(new JMenuItem(s));
  390.     }
  391.  
  392.     /**
  393.      * Creates a new menuitem attached to the specified 
  394.      * Action object and appends it to the end of this menu.
  395.      *
  396.      * @param a the Action for the menuitem to be added
  397.      * @see Action
  398.      */
  399.     public JMenuItem add(Action a) {
  400.         JMenuItem mi = new JMenuItem((String)a.getValue(Action.NAME),
  401.                                      (Icon)a.getValue(Action.SMALL_ICON));
  402.         mi.setHorizontalTextPosition(JButton.RIGHT);
  403.         mi.setVerticalTextPosition(JButton.CENTER);
  404.         mi.setEnabled(a.isEnabled());   
  405.         mi.addActionListener(a);
  406.         add(mi);
  407.         PropertyChangeListener actionPropertyChangeListener = 
  408.             createActionChangeListener(mi);
  409.     if (listenerRegistry == null) {
  410.         listenerRegistry = new Hashtable();
  411.     }
  412.     listenerRegistry.put(mi, a);
  413.     listenerRegistry.put(a, actionPropertyChangeListener);
  414.         a.addPropertyChangeListener(actionPropertyChangeListener);
  415.         return mi;
  416.     }
  417.  
  418.     protected PropertyChangeListener createActionChangeListener(JMenuItem b) {
  419.         return new ActionChangedListener(b);
  420.     }
  421.  
  422.     private class ActionChangedListener implements PropertyChangeListener {
  423.         JMenuItem menuItem;
  424.         
  425.         ActionChangedListener(JMenuItem mi) {
  426.             super();
  427.         setTarget(mi);
  428.         }
  429.         public void propertyChange(PropertyChangeEvent e) {
  430.             String propertyName = e.getPropertyName();
  431.             if (e.getPropertyName().equals(Action.NAME)) {
  432.                 String text = (String) e.getNewValue();
  433.                 menuItem.setText(text);
  434.             } else if (propertyName.equals("enabled")) {
  435.                 Boolean enabledState = (Boolean) e.getNewValue();
  436.                 menuItem.setEnabled(enabledState.booleanValue());
  437.             } else if (e.getPropertyName().equals(Action.SMALL_ICON)) {
  438.                 Icon icon = (Icon) e.getNewValue();
  439.                 menuItem.setIcon(icon);
  440.                 menuItem.invalidate();
  441.                 menuItem.repaint();
  442.             } 
  443.         }
  444.     public void setTarget(JMenuItem b) {
  445.         this.menuItem = b;
  446.     }
  447.     }
  448.  
  449.     /**
  450.      * Append a new separator to the end of the menu.
  451.      */
  452.     public void addSeparator()
  453.     {
  454.         ensurePopupMenuCreated();
  455.         popupMenu.addSeparator();
  456.     }
  457.  
  458.     /**
  459.      * Insert a new menuitem with the specified text at a 
  460.      * given position.
  461.      *
  462.      * @param s the text for the menuitem to add
  463.      * @param pos an int giving the position at which to add the 
  464.      *               new menuitem
  465.      */
  466.     public void insert(String s, int pos) {
  467.         if (pos < 0) {
  468.             throw new IllegalArgumentException("index less than zero.");
  469.         }
  470.  
  471.         ensurePopupMenuCreated();
  472.         popupMenu.insert(new JMenuItem(s), pos);
  473.     }
  474.  
  475.     /**
  476.      * Insert the specified JMenuitem at a given position.
  477.      *
  478.      * @param mi the JMenuitem to add
  479.      * @param pos an int giving the position at which to add the 
  480.      *               new JMenuitem
  481.      */
  482.     public JMenuItem insert(JMenuItem mi, int pos) {
  483.         if (pos < 0) {
  484.             throw new IllegalArgumentException("index less than zero.");
  485.         }
  486.         AccessibleContext ac = mi.getAccessibleContext();
  487.         ac.setAccessibleParent(this);
  488.         ensurePopupMenuCreated();
  489.         popupMenu.insert(mi, pos);
  490.         return mi;
  491.     }
  492.  
  493.     /**
  494.      * Insert a new menuitem attached to the specified Action 
  495.      * object at a given position.
  496.      *
  497.      * @param a the Action object for the menuitem to add
  498.      * @param pos an int giving the position at which to add the 
  499.      *               new menuitem
  500.      */
  501.     public JMenuItem insert(Action a, int pos) {
  502.         if (pos < 0) {
  503.             throw new IllegalArgumentException("index less than zero.");
  504.         }
  505.  
  506.         ensurePopupMenuCreated();
  507.         JMenuItem mi = new JMenuItem((String)a.getValue(Action.NAME));
  508.         mi.addActionListener(a);
  509.         popupMenu.insert(mi, pos);
  510.         a.addPropertyChangeListener(new ActionChangedListener(mi));
  511.         return mi;
  512.     }
  513.  
  514.     /**
  515.      * Inserts a separator at the specified position.
  516.      *
  517.      * @param       index an int giving the position at which to 
  518.      *                    insert the menu separator
  519.      * @exception   IllegalArgumentException if the value of 
  520.      *                       <code>index</code> is less than 0.
  521.      */
  522.     public void insertSeparator(int index) {
  523.         if (index < 0) {
  524.             throw new IllegalArgumentException("index less than zero.");
  525.         }
  526.  
  527.         ensurePopupMenuCreated();
  528.         popupMenu.insert( new JPopupMenu.Separator(), index );
  529.     }
  530.  
  531.     /** 
  532.      * Returns the JMenuItem at the specified position.
  533.      * If the specified position contains a separator, this JMenu
  534.      * is returned.  
  535.      *
  536.      * @param pos    an int giving the position
  537.      * @exception   IllegalArgumentException if the value of 
  538.      *                       <code>index</code> is less than 0.
  539.      */
  540.     public JMenuItem getItem(int pos) {
  541.         if (pos < 0) {
  542.             throw new IllegalArgumentException("index less than zero.");
  543.         }
  544.  
  545.         Component c = getMenuComponent(pos);
  546.         if (c instanceof JMenuItem) {
  547.             JMenuItem mi = (JMenuItem) c;
  548.             return mi;
  549.         }
  550.         // PENDING(ges): Should probably do something better here
  551.         return this;
  552.     }
  553.  
  554.     /**
  555.      * Returns the number of items on the menu, including separators.
  556.      * This method is included for AWT compatibility.
  557.      *
  558.      * @return an int equal to the number of items on the menu
  559.      * @see #getMenuComponentCount
  560.      */
  561.     public int getItemCount() {
  562.         return getMenuComponentCount();
  563.     }
  564.  
  565.     /**
  566.      * Returns true if the menu can be torn off.
  567.      *
  568.      * @return true if the menu can be torn off, else false
  569.      */
  570.     public boolean isTearOff() {
  571.         throw new Error("boolean isTearOff() {} not yet implemented");
  572.     }
  573.  
  574.     /**
  575.      * Removes the specified menu item from this menu.
  576.      *
  577.      * @param       item the JMenuItem to be removed from the menu
  578.      */
  579.     public void remove(JMenuItem item) {
  580.         popupMenu.remove(item);
  581.     if (listenerRegistry != null) { 
  582.         ActionChangedListener p = (ActionChangedListener)listenerRegistry.remove(item);
  583.         Action a = (Action)listenerRegistry.remove(p);
  584.         item.removeActionListener(a);
  585.         if (p!=null)
  586.         p.setTarget(null);
  587.         if (a!=null)
  588.         a.removePropertyChangeListener(p);
  589.     }
  590.     }
  591.  
  592.     /**
  593.      * Removes the menu item at the specified index from this menu.
  594.      *
  595.      * @param       index the position of the item to be removed. 
  596.      * @exception   IllegalArgumentException if the value of 
  597.      *                       <code>index</code> is less than 0.
  598.      */
  599.     public void remove(int pos) {
  600.         if (pos < 0) {
  601.             throw new IllegalArgumentException("index less than zero.");
  602.         }
  603.  
  604.         popupMenu.remove(pos);
  605.     }
  606.  
  607.     /**
  608.      * Removes the Component from this menu.
  609.      *
  610.      * @param       c the component to be removed
  611.      */
  612.     public void remove(Component c) {
  613.         popupMenu.remove(c);
  614.     }
  615.  
  616.     /**
  617.      * Remove all menu items from this menu.
  618.      */
  619.     public void removeAll() {
  620.         int nitems = getMenuComponentCount();
  621.         for (int i = 0 ; i < nitems ; i++) {
  622.             remove(0);
  623.         }
  624.     }
  625.  
  626.     /**
  627.      * Returns the number of components on the menu.
  628.      *
  629.      * @return an int -- the number of components on the menu
  630.      */
  631.     public int getMenuComponentCount() {
  632.         int componentCount = 0;
  633.         if (popupMenu != null)
  634.             componentCount = popupMenu.getComponentCount();
  635.         return componentCount;
  636.     }
  637.  
  638.     /**
  639.      * Returns the component at position n
  640.      *
  641.      * @param n the position of the component to be returned
  642.      */
  643.     public Component getMenuComponent(int n) {
  644.         if (popupMenu != null)
  645.             return popupMenu.getComponent(n);
  646.         
  647.         return null;
  648.     }
  649.  
  650.     /**
  651.      * Returns an array of the menu's subcomponents
  652.      *
  653.      * @return an array of Components
  654.      */
  655.     public Component[] getMenuComponents() {
  656.         if (popupMenu != null)
  657.             return popupMenu.getComponents();
  658.         
  659.         return new Component[0];
  660.     }
  661.  
  662.     /**
  663.      * Returns true if the menu is a 'top-level menu', that is, if it is
  664.      * the direct child of a menubar.
  665.      *
  666.      * @return true if the menu is activated from the menu bar,
  667.      *         false if the menu is activated from a menu item
  668.      *         on another menu
  669.      */
  670.     public boolean isTopLevelMenu() {
  671.         if (getParent() instanceof JMenuBar)
  672.             return true;
  673.         
  674.         return false;
  675.     }
  676.  
  677.     /**
  678.      * Returns true if the specified component exists in the 
  679.      * submenu hierarchy.
  680.      *
  681.      * @param c the Component to be tested
  682.      * @return true if the component exists
  683.      */
  684.     public boolean isMenuComponent(Component c) {
  685.         // Are we in the MenuItem part of the menu
  686.         if (c == this)
  687.             return true;
  688.         // Are we in the PopupMenu?
  689.         if (c instanceof JPopupMenu) {
  690.             JPopupMenu comp = (JPopupMenu) c;
  691.             if (comp == this.getPopupMenu())
  692.                 return true;
  693.         }
  694.         // Are we in a Component on the PopupMenu
  695.         int ncomponents = this.getMenuComponentCount();
  696.         Component[] component = this.getMenuComponents();
  697.         for (int i = 0 ; i < ncomponents ; i++) {
  698.             Component comp = component[i];
  699.             // Are we in the current component?
  700.             if (comp == c)
  701.                 return true;
  702.             // Hmmm, what about Non-menu containers?
  703.  
  704.             // Recursive call for the Menu case
  705.             if (comp instanceof JMenu) {
  706.                 JMenu subMenu = (JMenu) comp;
  707.                 if (subMenu.isMenuComponent(c))
  708.                     return true;
  709.             }
  710.         }
  711.         return false;
  712.     }
  713.  
  714.  
  715.     /*
  716.      * Returns a point in the coordinate space of this menu's popupmenu
  717.      * which corresponds to the point p in the menu's coordinate space.
  718.      *
  719.      * @param p the point to be translated
  720.      */
  721.     private Point translateToPopupMenu(Point p) {
  722.         return translateToPopupMenu(p.x, p.y);
  723.     }
  724.  
  725.     /*
  726.      * Returns a point in the coordinate space of this menu's popupmenu
  727.      * which corresponds to the point (x,y) in the menu's coordinate space.
  728.      * @param x the x coordinate of the point to be translated
  729.      * @param y the y coordinate of the point to be translated
  730.      */
  731.     private Point translateToPopupMenu(int x, int y) {
  732.             int newX;
  733.             int newY;
  734.  
  735.             if (getParent() instanceof JPopupMenu) {
  736.                 newX = x - getSize().width;
  737.                 newY = y;
  738.             } else {
  739.                 newX = x;
  740.                 newY = y - getSize().height;
  741.             }
  742.  
  743.             return new Point(newX, newY);
  744.         }
  745.  
  746.     /**
  747.      * Returns the popupmenu associated with this menu
  748.      */
  749.     public JPopupMenu getPopupMenu() {
  750.         ensurePopupMenuCreated();
  751.         return popupMenu;
  752.     }
  753.  
  754.     /**
  755.      * Add a listener for menu events
  756.      *
  757.      * @param l the listener to be added
  758.      */
  759.     public void addMenuListener(MenuListener l) {
  760.         listenerList.add(MenuListener.class, l);
  761.     }
  762.     
  763.     /**
  764.      * Remove a listener for menu events
  765.      *
  766.      * @param l the listener to be removed
  767.      */
  768.     public void removeMenuListener(MenuListener l) {
  769.         listenerList.remove(MenuListener.class, l);
  770.     }
  771.  
  772.     /**
  773.      * Notify all listeners that have registered interest for
  774.      * notification on this event type.  The event instance 
  775.      * is lazily created using the parameters passed into 
  776.      * the fire method.
  777.      *
  778.      * @see EventListenerList
  779.      */
  780.     protected void fireMenuSelected() {
  781.         // Guaranteed to return a non-null array
  782.         Object[] listeners = listenerList.getListenerList();
  783.         // Process the listeners last to first, notifying
  784.         // those that are interested in this event
  785.         for (int i = listeners.length-2; i>=0; i-=2) {
  786.             if (listeners[i]==MenuListener.class) {
  787.                 if (listeners[i+1]== null) {
  788.                     throw new Error(getText() +" has a NULL Listener!! " 
  789.                                        + i);
  790.                 } else {
  791.                     // Lazily create the event:
  792.                     if (menuEvent == null)
  793.                         menuEvent = new MenuEvent(this);
  794.                     ((MenuListener)listeners[i+1]).menuSelected(menuEvent);
  795.                 }              
  796.             }
  797.         }
  798.     }
  799.  
  800.     /**
  801.      * Notify all listeners that have registered interest for
  802.      * notification on this event type.  The event instance 
  803.      * is lazily created using the parameters passed into 
  804.      * the fire method.
  805.      *
  806.      * @see EventListenerList
  807.      */
  808.     protected void fireMenuDeselected() {
  809.         // Guaranteed to return a non-null array
  810.         Object[] listeners = listenerList.getListenerList();
  811.         // Process the listeners last to first, notifying
  812.         // those that are interested in this event
  813.         for (int i = listeners.length-2; i>=0; i-=2) {
  814.             if (listeners[i]==MenuListener.class) {
  815.                 if (listeners[i+1]== null) {
  816.                     System.out.println(getText() +" has a NULL Listener!! " 
  817.                                        + i);
  818.                 } else {
  819.                     // Lazily create the event:
  820.                     if (menuEvent == null)
  821.                         menuEvent = new MenuEvent(this);
  822.                     ((MenuListener)listeners[i+1]).menuDeselected(menuEvent);
  823.                 }              
  824.             }
  825.         }
  826.     }
  827.  
  828.     /**
  829.      * Notify all listeners that have registered interest for
  830.      * notification on this event type.  The event instance 
  831.      * is lazily created using the parameters passed into 
  832.      * the fire method.
  833.      *
  834.      * @see EventListenerList
  835.      */
  836.     protected void fireMenuCanceled() {
  837.         // Guaranteed to return a non-null array
  838.         Object[] listeners = listenerList.getListenerList();
  839.         // Process the listeners last to first, notifying
  840.         // those that are interested in this event
  841.         for (int i = listeners.length-2; i>=0; i-=2) {
  842.             if (listeners[i]==MenuListener.class) {
  843.                 if (listeners[i+1]== null) {
  844.                     throw new Error(getText() +" has a NULL Listener!! " 
  845.                                        + i);
  846.                 } else {
  847.                     // Lazily create the event:
  848.                     if (menuEvent == null)
  849.                         menuEvent = new MenuEvent(this);
  850.                     ((MenuListener)listeners[i+1]).menuCanceled(menuEvent);
  851.                 }              
  852.             }
  853.         }
  854.     }
  855.  
  856.     class MenuChangeListener implements ChangeListener, Serializable {
  857.         boolean isSelected = false;
  858.         public void stateChanged(ChangeEvent e) {               
  859.             ButtonModel model = (ButtonModel) e.getSource();
  860.             boolean modelSelected = model.isSelected();
  861.             // System.out.println("Model selected = " + modelSelected +
  862.             // " isSelected = " + isSelected);
  863.             if (modelSelected != isSelected) {
  864.                 if (modelSelected == true) {
  865.                     fireMenuSelected();
  866.                 } else {
  867.                     fireMenuDeselected();
  868.                 }
  869.                 isSelected = modelSelected;
  870.             }
  871.         }
  872.     }
  873.  
  874.     private ChangeListener createMenuChangeListener() {
  875.         return new MenuChangeListener();
  876.     }
  877.  
  878.  
  879.     /**
  880.      * Create a window-closing listener for the popup.
  881.      *
  882.      * @param p the JPopupMenu
  883.      * @see WinListener
  884.      */
  885.     protected WinListener createWinListener(JPopupMenu p) {
  886.         return new WinListener(p);
  887.     }
  888.  
  889.     /**
  890.      * A listener class that watches for a popup window closing.
  891.      * When the popup is closing, the listener deselects the menu.
  892.      * <p>
  893.      * <strong>Warning:</strong>
  894.      * Serialized objects of this class will not be compatible with
  895.      * future Swing releases.  The current serialization support is appropriate
  896.      * for short term storage or RMI between applications running the same
  897.      * version of Swing.  A future release of Swing will provide support for
  898.      * long term persistence.
  899.      */
  900.     protected class WinListener extends WindowAdapter implements Serializable {
  901.         JPopupMenu popupMenu;
  902.         /**
  903.          *  Create the window listener for the specified popup.
  904.          */
  905.         public WinListener(JPopupMenu p) {
  906.             this.popupMenu = p;
  907.         }
  908.         /**
  909.          * Deselect the menu when the popup is closed from outside.
  910.          */
  911.         public void windowClosing(WindowEvent e) {
  912.             setSelected(false);
  913.         }
  914.     }
  915.  
  916.     /**
  917.      * Messaged when the menubar selection changes to activate or
  918.      * deactivate this menu.
  919.      * Overrides <code>JMenuItem.menuSelectionChanged</code>.
  920.      *
  921.      * @param isIncluded  true if this menu is active, false if
  922.      *        it is not
  923.      */
  924.     public void menuSelectionChanged(boolean isIncluded) {
  925.         setSelected(isIncluded);
  926.     }
  927.  
  928.     /**
  929.      * Returns an array containing the sub-menu components for this menu component
  930.      * @return an array of MenuElement objects
  931.      */
  932.     public MenuElement[] getSubElements() {
  933.         if(popupMenu == null)
  934.             return new MenuElement[0];
  935.         else {
  936.             MenuElement result[] = new MenuElement[1];
  937.             result[0] = popupMenu;
  938.             return result;
  939.         }
  940.     }
  941.  
  942.     
  943.     // implements javax.swing.MenuElement
  944.     /**
  945.      * This method returns the java.awt.Component used to paint this MenuElement.
  946.      * The returned component is used to convert events and detect if an event is inside
  947.      * a menu component.
  948.      */   
  949.     public Component getComponent() {
  950.         return this;
  951.     }
  952.  
  953.  
  954.     /** 
  955.      * setAccelerator() is not defined for JMenu.  Use setMnemonic() instead. 
  956.      *
  957.      * @beaninfo
  958.      *     description: The keystroke combination which will invoke the JMenuItem's
  959.      *                  actionlisteners without navigating the menu hierarchy
  960.      *          hidden: true
  961.      */
  962.     public void setAccelerator(KeyStroke keyStroke) {
  963.         throw new Error("setAccelerator() is not defined for JMenu.  Use setMnemonic() instead.");
  964.     }
  965.  
  966.     /**
  967.      *
  968.      */
  969.     protected void processKeyEvent(KeyEvent e) {
  970.         MenuSelectionManager.defaultManager().processKeyEvent(e);
  971.     if(e.isConsumed())
  972.             return;
  973.     /* The "if" block below  fixes bug #4108907.
  974.        Without this code, opened menus that
  975.        weren't interested in TAB key events (most menus are not) would
  976.        allow such events to propagate up until a component was found
  977.        that was interested in the event. This would often result in
  978.        the focus being moved to another component as a result of the
  979.        TAB, while the menu stayed open. The behavior that is most
  980.        probably desired is that menus are modal, and thus consume
  981.        all keyboard events while they are open. This is implemented
  982.        by the inner "if" clause. But if the desired behavior on TABs
  983.        is that the menu should close and allow the focus to move,
  984.        the "else" clause takes care of that. Note that this is probably
  985.        not the right way to implement that behavior; instead, the menu
  986.        should unpost whenever it looses focus, which would also fix
  987.        another bug: 4156858.
  988.        The fact that one has to special-case TABS here in JMenu code
  989.        also offends me...
  990.        hania 23 July 1998 */
  991.     if(isSelected() && (e.getKeyCode() == KeyEvent.VK_TAB
  992.        || e.getKeyChar() == '\t')) {
  993.       if ((Boolean) UIManager.get("Menu.consumesTabs") == Boolean.TRUE) {
  994.         e.consume();
  995.         return;
  996.       } else {
  997.         MenuSelectionManager.defaultManager().clearSelectedPath();
  998.       }
  999.     }
  1000.         super.processKeyEvent(e);
  1001.     }
  1002.  
  1003.     /**
  1004.      * Programatically perform a "click".  This overrides the method
  1005.      * AbstractButton.doClick(int) in order to make the menu pop up.
  1006.      */
  1007.     public void doClick(int pressTime) {
  1008.     MenuElement me[] = buildMenuElementArray(this);
  1009.     MenuSelectionManager.defaultManager().setSelectedPath(me);
  1010.     }
  1011.  
  1012.     /*
  1013.      * Build an array of menu elements - from my PopupMenu to the root
  1014.      * JMenuBar
  1015.      */
  1016.     private MenuElement[] buildMenuElementArray(JMenu leaf) {
  1017.     Vector elements = new Vector();
  1018.     Component current = leaf.getPopupMenu();
  1019.     JPopupMenu pop;
  1020.     JMenu menu;
  1021.     JMenuBar bar;
  1022.  
  1023.     while (true) {
  1024.         if (current instanceof JPopupMenu) {
  1025.         pop = (JPopupMenu) current;
  1026.         elements.insertElementAt(pop, 0);
  1027.         current = pop.getInvoker();
  1028.         } else if (current instanceof JMenu) {
  1029.         menu = (JMenu) current;
  1030.         elements.insertElementAt(menu, 0);
  1031.         current = menu.getParent();
  1032.         } else if (current instanceof JMenuBar) {
  1033.         bar = (JMenuBar) current;
  1034.         elements.insertElementAt(bar, 0);
  1035.         MenuElement me[] = new MenuElement[elements.size()];
  1036.         elements.copyInto(me);
  1037.         return me;
  1038.         }
  1039.     }
  1040.     }
  1041.  
  1042.  
  1043.     /** 
  1044.      * See readObject() and writeObject() in JComponent for more 
  1045.      * information about serialization in Swing.
  1046.      */
  1047.     private void writeObject(ObjectOutputStream s) throws IOException {
  1048.         s.defaultWriteObject();
  1049.     if ((ui != null) && (getUIClassID().equals(uiClassID))) {
  1050.         ui.installUI(this);
  1051.     }
  1052.     }
  1053.  
  1054.  
  1055.     /**
  1056.      * Returns a string representation of this JMenu. This method 
  1057.      * is intended to be used only for debugging purposes, and the 
  1058.      * content and format of the returned string may vary between      
  1059.      * implementations. The returned string may be empty but may not 
  1060.      * be <code>null</code>.
  1061.      * <P>
  1062.      * Overriding paramString() to provide information about the
  1063.      * specific new aspects of the JFC components.
  1064.      * 
  1065.      * @return  a string representation of this JMenu.
  1066.      */
  1067.     protected String paramString() {
  1068.     return super.paramString();
  1069.     }
  1070.  
  1071.  
  1072. /////////////////
  1073. // Accessibility support
  1074. ////////////////
  1075.  
  1076.     /**
  1077.      * Get the AccessibleContext associated with this JComponent
  1078.      *
  1079.      * @return the AccessibleContext of this JComponent
  1080.      */
  1081.     public AccessibleContext getAccessibleContext() {
  1082.         if (accessibleContext == null) {
  1083.             accessibleContext = new AccessibleJMenu();
  1084.         }
  1085.         return accessibleContext;
  1086.     }
  1087.  
  1088.     /**
  1089.      * The class used to obtain the accessible role for this object.
  1090.      * <p>
  1091.      * <strong>Warning:</strong>
  1092.      * Serialized objects of this class will not be compatible with
  1093.      * future Swing releases.  The current serialization support is appropriate
  1094.      * for short term storage or RMI between applications running the same
  1095.      * version of Swing.  A future release of Swing will provide support for
  1096.      * long term persistence.
  1097.      */
  1098.     protected class AccessibleJMenu extends AccessibleJMenuItem 
  1099.     implements AccessibleSelection {
  1100.  
  1101.         /**
  1102.          * Returns the number of accessible children in the object.  If all
  1103.          * of the children of this object implement Accessible, than this
  1104.          * method should return the number of children of this object.
  1105.          *
  1106.          * @return the number of accessible children in the object.
  1107.          */
  1108.         public int getAccessibleChildrenCount() {
  1109.             Component[] children = getMenuComponents();
  1110.             int count = 0;
  1111.             for (int j = 0; j < children.length; j++) {
  1112.                 if (children[j] instanceof Accessible) {
  1113.                     count++;
  1114.                 }
  1115.             }
  1116.             return count;
  1117.         }
  1118.  
  1119.         /**
  1120.          * Return the nth Accessible child of the object.  
  1121.          *
  1122.          * @param i zero-based index of child
  1123.          * @return the nth Accessible child of the object
  1124.          */
  1125.         public Accessible getAccessibleChild(int i) {
  1126.             Component[] children = getMenuComponents();
  1127.             int count = 0;
  1128.             for (int j = 0; j < children.length; j++) {
  1129.                 if (children[j] instanceof Accessible) {
  1130.                     if (count == i) {
  1131.                         if (children[j] instanceof JComponent) {
  1132.                             // FIXME:  [[[WDW - probably should set this when
  1133.                             // the component is added to the menu.  I tried
  1134.                             // to do this in most cases, but the separators
  1135.                             // added by addSeparator are hard to get to.]]]
  1136.                             AccessibleContext ac = ((Accessible) children[j]).getAccessibleContext();
  1137.                             ac.setAccessibleParent(JMenu.this);
  1138.                         }
  1139.                         return (Accessible) children[j];
  1140.                     } else {
  1141.                         count++;
  1142.                     }
  1143.                 }
  1144.             }
  1145.             return null;
  1146.         }
  1147.  
  1148.         /**
  1149.          * Get the role of this object.
  1150.          *
  1151.          * @return an instance of AccessibleRole describing the role of the 
  1152.          * object
  1153.          * @see AccessibleRole
  1154.          */
  1155.         public AccessibleRole getAccessibleRole() {
  1156.             return AccessibleRole.MENU;
  1157.         }
  1158.  
  1159.         /**
  1160.          * Get the AccessibleSelection associated with this object if one
  1161.          * exists.  Otherwise return null.
  1162.          */
  1163.         public AccessibleSelection getAccessibleSelection() {
  1164.             return this;
  1165.         }
  1166.  
  1167.         /**
  1168.          * Returns 1 if a sub-menu is currently selected in this menu.
  1169.          *
  1170.          * @return 1 if a menu is currently selected, else 0
  1171.          */
  1172.         public int getAccessibleSelectionCount() {
  1173.         MenuElement me[] =
  1174.         MenuSelectionManager.defaultManager().getSelectedPath();
  1175.         if (me != null) {
  1176.         for (int i = 0; i < me.length; i++) {
  1177.             if (me[i] == JMenu.this) {   // this menu is selected
  1178.             if (i+1 < me.length) {
  1179.                 return 1;
  1180.             }
  1181.             }
  1182.         }
  1183.         }
  1184.         return 0;
  1185.         }
  1186.  
  1187.         /**
  1188.          * Returns the currently selected sub-menu if one is selected,
  1189.          * otherwise null (there can only be one selection, and it can
  1190.      * only be a sub-menu, as otherwise menu items don't remain
  1191.      * selected).
  1192.          */
  1193.         public Accessible getAccessibleSelection(int i) {
  1194.         // if i is a sub-menu & popped, return it
  1195.         if (i < 0 || i >= getItemCount()) {
  1196.         return null;
  1197.         }
  1198.         MenuElement me[] = 
  1199.         MenuSelectionManager.defaultManager().getSelectedPath();
  1200.             if (me != null) {
  1201.         for (int j = 0; j < me.length; j++) {
  1202.             if (me[j] == JMenu.this) {   // this menu is selected
  1203.             // so find the next JMenuItem in the MenuElement 
  1204.             // array, and return it!
  1205.             while (++j < me.length) {
  1206.                 if (me[j] instanceof JMenuItem) {
  1207.                 return (Accessible) me[j];
  1208.                 }
  1209.             }
  1210.             }
  1211.         }
  1212.         }
  1213.         return null;
  1214.         }
  1215.  
  1216.         /**
  1217.          * Returns true if the current child of this object is selected.
  1218.      * (i.e. if this child is a pop-ed up sub-menu)
  1219.          *
  1220.          * @param i the zero-based index of the child in this Accessible
  1221.          * object.
  1222.          * @see AccessibleContext#getAccessibleChild
  1223.          */
  1224.         public boolean isAccessibleChildSelected(int i) {
  1225.         // if i is a sub-menu and is pop-ed up, return true, else false
  1226.         MenuElement me[] = 
  1227.         MenuSelectionManager.defaultManager().getSelectedPath();
  1228.         if (me != null) {
  1229.         JMenuItem mi = JMenu.this.getItem(i);
  1230.         for (int j = 0; j < me.length; j++) {
  1231.             if (me[j] == mi) {
  1232.             return true;
  1233.             }
  1234.         }
  1235.         }
  1236.         return false;
  1237.         }
  1238.  
  1239.  
  1240.         /**
  1241.          * Selects the nth menu in the menu.  If that item is a sub-menu,
  1242.          * it will pop up in response.  If a different item is already
  1243.      * popped up, this will force it to close.  If this is a sub-menu
  1244.      * that is already poppoed up (selected), this method has no
  1245.      * effect.
  1246.          *
  1247.          * @param i the zero-based index of selectable items
  1248.          * @see #getAccessibleStateSet
  1249.          */
  1250.         public void addAccessibleSelection(int i) {
  1251.         if (i < 0 || i >= getItemCount()) {
  1252.         return;
  1253.         }
  1254.         JMenuItem mi = getItem(i);
  1255.         if (mi != null) {
  1256.         if (mi instanceof JMenu) {
  1257.             MenuElement me[] = buildMenuElementArray((JMenu) mi);
  1258.             MenuSelectionManager.defaultManager().setSelectedPath(me);
  1259.         } else {
  1260.             mi.doClick();
  1261.             MenuSelectionManager.defaultManager().setSelectedPath(null);
  1262.             }
  1263.         }
  1264.         }
  1265.  
  1266.         /**
  1267.          * Removes the nth item from the selection.  In general, menus 
  1268.      * can only have one item within them selected at a time 
  1269.      * (e.g. one sub-menu popped open).
  1270.          *
  1271.          * @param i the zero-based index of the selected item
  1272.          */
  1273.         public void removeAccessibleSelection(int i) {
  1274.         if (i < 0 || i >= getItemCount()) {
  1275.         return;
  1276.         }
  1277.         JMenuItem mi = getItem(i);
  1278.         if (mi != null && mi instanceof JMenu) {
  1279.         if (((JMenu) mi).isSelected()) {
  1280.             MenuElement old[] =
  1281.             MenuSelectionManager.defaultManager().getSelectedPath();
  1282.             MenuElement me[] = new MenuElement[old.length-1];
  1283.             for (int j = 0; j < old.length -1; j++) {
  1284.             me[j] = old[j];
  1285.             }
  1286.             MenuSelectionManager.defaultManager().setSelectedPath(me);
  1287.         }
  1288.             }
  1289.         }
  1290.  
  1291.         /**
  1292.          * Clears the selection in the object, so that nothing in the
  1293.          * object is selected.  This will close any open sub-menu.
  1294.          */
  1295.         public void clearAccessibleSelection() {
  1296.         // if this menu is selected, reset selection to only go
  1297.         // to this menu; else do nothing
  1298.         MenuElement old[] = 
  1299.         MenuSelectionManager.defaultManager().getSelectedPath();
  1300.         if (old != null) {
  1301.         for (int j = 0; j < old.length; j++) {
  1302.             if (old[j] == JMenu.this) {  // menu is in the selection!
  1303.             MenuElement me[] = new MenuElement[j+1];
  1304.             System.arraycopy(old, 0, me, 0, j);
  1305.             me[j] = JMenu.this.getPopupMenu();
  1306.             MenuSelectionManager.defaultManager().setSelectedPath(me);
  1307.             }
  1308.         }
  1309.             }
  1310.         }
  1311.  
  1312.         /**
  1313.          * Normally causes every selected item in the object to be selected
  1314.          * if the object supports multiple selections.  This method
  1315.          * makes no sense in a menu bar, and so does nothing.
  1316.          */
  1317.         public void selectAllAccessibleSelection() {
  1318.         }
  1319.     } // inner class AccessibleJMenu
  1320. }
  1321.  
  1322.